using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.IO.Ports;
using System.Threading;
using System.IO;
using Microsoft.Win32;
using System.Net;
using System.Net.Sockets;



namespace HardwareInterfaceManagement
    {
  

    public class CommunicationsManager
        {
         
            public SerialPort MyConnection;
            public bool ConnectionActive = false;
            public MessageDetect MyPacketDetector;
            public int BytesRead = 0;
            public string COMPort = "COM1";
            public bool UseingTCP = false;
            public IPEndPoint ipe = null;

    
        
            private int ManagementState = 0;
            private const int WAITING_FOR_CONNECT_COMMAND = 0x00;
            private const int SCANNING_FOR_MODULE = 0x01;
            private const int CONNECTING = 0x02;
            private const int CONNECTION_ACTIVE = 0x03;
            private const int WAIT_FOR_RECONNECT = 0x04;
            private const int ATTEMPT_TO_CLOSE = 0x05;
            private const int RECONNECT_PERIOD = 1000;
         
            private bool AttemptToConnect = false;
            private bool AttempToClose = false;

            public string[] ManagerMessages = { "Disconnected", "Connecting", "Connected", "Waiting before retry", "Connection Exception","Closing" };
            public string ConnectionStatus; 
            private Thread CommunicationsManagerThread;

            bool ForceThreadExit = false;

           public CommunicationsManager()
                {
                        ManagementState = WAITING_FOR_CONNECT_COMMAND;
                      
                        ConnectionStatus = ManagerMessages[0];
                        ConnectionActive = false;
                        AttemptToConnect = false;
                        AttempToClose = false;
                        MyPacketDetector = new MessageDetect();
                        CommunicationsManagerThread = new Thread(new ThreadStart(Tick));
                     //   CommunicationsManagerThread.Priority = ThreadPriority.Highest;
                        CommunicationsManagerThread.Start();
                        ForceThreadExit = false;
                
                }
            
           public void Tick()
                {
                   while (true)
                    {
                        if (ForceThreadExit == true)
                        {
                            return;
                        }
                        Thread.Sleep(10);
                        //This Entire Routine Needs to be in a try catch for Virtual Com ports.  
                        try
                        {

                            //here is the main state machine 

                            switch (ManagementState)
                            {
                  
                               
                                case WAITING_FOR_CONNECT_COMMAND:

                                    Thread.Sleep(200);

                                    ConnectionStatus = ManagerMessages[0];
                                    
                                    if (AttemptToConnect == true)
                                        {
                                        ManagementState = SCANNING_FOR_MODULE;
                                        AttemptToConnect = false;
                                        MyConnection = null;
                                        GC.Collect();
                                    
                                        }
                                    else
                                        {
                                        ManagementState = WAITING_FOR_CONNECT_COMMAND;
                                        }

                                    break;
                                
                                case SCANNING_FOR_MODULE:

                                    //First, check to see if there is a Virutalcom port available in the registry
                                    ConnectionActive = false;
                                    ConnectionStatus = ManagerMessages[1];
                                   
                                    ManagementState = CONNECTING;

                                    MyConnection = null;
                                    GC.Collect();
                                    
                                     
                                    break;

                                case CONNECTING:
                                    ConnectionStatus = ManagerMessages[1];
                                    ConnectionActive = false;

                                    if (UseingTCP)
                                    {
                                        try
                                        {
                                            TcpClient client = new TcpClient();
                                        }
                                        catch (Exception Ex)
                                        {
                                            Thread.Sleep(RECONNECT_PERIOD);
                                            ManagementState = WAITING_FOR_CONNECT_COMMAND;
                                            MessageBox.Show(Ex.Message);
                                        }

                                    }

                                    else
                                    {


                                        try
                                        {
                                            MyConnection = new SerialPort();
                                            //MyConnection.PortName = RegistryCOMPortRecord;
                                            MyConnection.PortName = COMPort;
                                            MyConnection.BaudRate = 57600;
                                            MyConnection.DataBits = 8;
                                            MyConnection.StopBits = StopBits.One;
                                            MyConnection.Handshake = Handshake.None;

                                            MyConnection.DtrEnable = true;
                                            MyConnection.RtsEnable = true;
                    
                                            MyConnection.Open();
                                            ManagementState = CONNECTION_ACTIVE;
                                            BytesRead = 0;
                                        }

                                        catch (Exception Ex)
                                        {
                                            Thread.Sleep(RECONNECT_PERIOD);
                                            ManagementState = WAITING_FOR_CONNECT_COMMAND;
                                            MessageBox.Show(Ex.Message);
                                        }
                                    }
                                    break;

                                case CONNECTION_ACTIVE:
     
                                    ConnectionActive = true;
                                    ConnectionStatus = ManagerMessages[2] + " ["+ this.MyConnection.PortName +"]";
                                    ManagementState = CONNECTION_ACTIVE;

                                  
                                    if(AttempToClose == true)
                                    {
                                        ConnectionActive = true;
                                        ManagementState = ATTEMPT_TO_CLOSE;
                                         ConnectionStatus = ManagerMessages[5];
                                    }
                                    else
                                    {

                                        int  BytesToGrab = MyConnection.BytesToRead;
                                            if (BytesToGrab > 0)
                                            {

                                                byte [] StreamBuffer = new byte[BytesToGrab];
                                                
                                                MyConnection.Read(StreamBuffer,0,BytesToGrab);
                                                BytesRead += BytesToGrab;
                                               
                                                    try
                                                    {

                                                        foreach (byte Data in StreamBuffer)
                                                        {
                                                            MyPacketDetector.ParseStream(Data);
                                                        }
                                                    }
                                                    catch (Exception Ex)
                                                    {
                                                        MessageBox.Show(Ex.Message);
                                                    }
                                                
                                            }
                                     }

                                    break;

                                case ATTEMPT_TO_CLOSE:
                                    if (MyConnection != null)
                                        {
                                          try
                                           {
                                              MyConnection.DiscardInBuffer();
                                              MyConnection.DiscardOutBuffer();
                                             if(MyConnection.IsOpen == true)
                                             {
                                              MyConnection.Close();
                                     
                                              }
                                             ConnectionStatus = ManagerMessages[0];
                                              ConnectionActive = false;
                                              AttempToClose = false;
                                              AttemptToConnect = false;
                                              ManagementState = WAITING_FOR_CONNECT_COMMAND;
                                           }
                                          catch
                                          {
                                              AttemptToConnect = false;
                                          }
                                      }
                                break;

                                case WAIT_FOR_RECONNECT:
                                                                
                                    Thread.Sleep(RECONNECT_PERIOD);
                                    ManagementState = WAITING_FOR_CONNECT_COMMAND;
                                    break;

                                default:
                                    ConnectionActive = false;
                                    AttemptToConnect = false;
                                    ConnectionStatus = ManagerMessages[0];
                                    ManagementState = WAITING_FOR_CONNECT_COMMAND;
                                    break;

                            }

                        }

                        catch 
                        {

                            ConnectionActive = false;
                            ConnectionStatus = ManagerMessages[4];
                            Thread.Sleep(RECONNECT_PERIOD);
                            //this Exception is important to catch.  THis will allow a graceful recovery if someone
                            //pulls the plug  
                            ManagementState = WAITING_FOR_CONNECT_COMMAND;
                        }
                    }

                }
             
           public void Write(byte[] BufOut)
            {

                if (ConnectionActive == true)
                {
                    if (this.MyConnection.IsOpen == true)
                    {
                        MyConnection.Write(BufOut, 0, BufOut.Length);
                    }
                }

            }

           public void Close()
            {
                AttempToClose = true;               
            }

           public void Terminate()
            {
                ForceThreadExit = true;
            }

           public void Connect()
                {
                     AttemptToConnect = true;
                }
    }

    
    class MessageData
    {
        public byte Header1 = 0;
        public byte Header2 =0 ;
        public ushort Length = 0;
        public byte[] Payload = new byte[1024];
        public ushort CRC = 0;

        public MessageData()
        {

        }
    }

    public class MessageDetect
    {
        public Queue<byte[]> PacketQueue = new Queue<byte[]>();
        public bool Enable = false;

        MessageData IncomingMessage = new MessageData();

            //State Machine Macros
        const byte H1 = 0xAA;
        const byte H2 = 0xAA;
        const byte SCAN_FOR_HEADER1 = 0x00;
        const byte SCAN_FOR_HEADER2 = 0x01;
        const byte GRAB_LENGTH_LOW = 0x02;
        const byte GRAB_LENGTH_HIGH = 0x03;
        const byte GRAB_PAYLOAD = 0x04;
        const byte GRAB_CRC_LOW = 0x05;
        const byte GRAB_CRC_HIGH = 0x06;

        protected byte DetectState = SCAN_FOR_HEADER1;
        protected ushort DataCnt = 0;
        protected ushort CheckCRC = 0;
        protected byte NextADCByte;
        public int PacketsIn = 0;

        public void ParseStream(byte DataIn)
        {
            switch (DetectState)
            {

                case SCAN_FOR_HEADER1:
                    if (DataIn == H1)
                    {
                        DetectState = SCAN_FOR_HEADER2;
                        IncomingMessage.Header1 = DataIn;
                
                    }
                    else
                    {
                        DetectState = SCAN_FOR_HEADER1;
                    }


                    break;

                case SCAN_FOR_HEADER2:
                    if (DataIn == H2)
                    {
                        DetectState = GRAB_LENGTH_HIGH;
                        IncomingMessage.Header2 = DataIn;
                 
                    }
                    else
                    {
                        DetectState = SCAN_FOR_HEADER1;
                    }
                    break;

                case GRAB_LENGTH_HIGH:
                    IncomingMessage.Length = (ushort)(((ushort)DataIn) << 8);
                    DataCnt = 0;
                    DetectState = GRAB_LENGTH_LOW;
                    break;

                case GRAB_LENGTH_LOW:

                    IncomingMessage.Length |= (ushort)DataIn;
                    DetectState = GRAB_PAYLOAD;
                    
                    break;

                case GRAB_PAYLOAD:
                    IncomingMessage.Payload[DataCnt] = DataIn;
                    DataCnt++;

                    if (DataCnt == (IncomingMessage.Length))
                    {
                        DetectState = GRAB_CRC_HIGH;
                    }

                    else
                    {
                        DetectState = GRAB_PAYLOAD;
                    }

                    break;

                case GRAB_CRC_HIGH:

                    IncomingMessage.CRC = 0;
                    IncomingMessage.CRC = (ushort)(DataIn<<8);
                    DetectState = GRAB_CRC_LOW;

                    break;

                case GRAB_CRC_LOW:

                    lock (PacketQueue)
                    {
                        IncomingMessage.CRC |= (ushort)(DataIn);

                        DetectState = SCAN_FOR_HEADER1;

                            //Check CRC of everything (including header Bytes)
                           
                            CRC CRCCheck = new CRC();

                            CRCCheck.Step(IncomingMessage.Header1);
                            CRCCheck.Step(IncomingMessage.Header2);
                            CRCCheck.Step((byte)(IncomingMessage.Length>>8));
                            CRCCheck.Step((byte)(IncomingMessage.Length));
                            for(int i=0;i<IncomingMessage.Length;i++)
                            {
                                CRCCheck.Step(IncomingMessage.Payload[i]);
                            }

                            if (CRCCheck.CRCVal == IncomingMessage.CRC)
                            {
                                int i;
                                //if we get a good packet, pass it into the Packet Queue

                                byte[] NewMessage= new byte[IncomingMessage.Length];

                                for (i = 0; i < IncomingMessage.Length; i++)
                                {
                                    NewMessage[i] = IncomingMessage.Payload[i];
                                }
                                PacketQueue.Enqueue(NewMessage);
                                PacketsIn++;

                            }
                            else
                            {
                               //Bad packets are trapped here
                            }

                    }
                    break;


                default:
                    DetectState = SCAN_FOR_HEADER1;
                    break;

            }

        }
    }

}
